"""
单层神经网络作为感知机、线性回归算法、Logistic回归算法的父类
"""
from matplotlib import pyplot as plt
import numpy as np

class SingleLayerNeuralNetwork:
    """
    单层神经网络
    1、使用 SingleLayerNeuralNetwork(1000, 0) 来初始化单层神经网络
    2、使用 Object.getW() 来获取分类面参数
    3、使用 Object.getDimen() 来获取训练样本的维数
    """
    
    def __init__(self, iteration=10000, randomstate=0):
        '''
        初始化参数 iteration 和 randomstate
        iteration：  最大迭代次数
        randomstate：随机种子，决定训练起始样本
        '''
        # 最大迭代次数
        self.iteration = iteration
        # 随机种子，决定迭代起始样本
        self.randomstate = randomstate
        # 分类面参数 w_1--w_i
        self.W = None
        # 分类面参数 w_0
        self.w_0 = None
        # 训练样本维度
        self.dimen = None
        # 训练样本数据
        self.Xtrain = None
        # 训练样本标签
        self.Ytrain = None
        # 测试样本数据
        self.Xtest = None
        # 测试样本预测的结果
        self.Ypred = None
        # 训练样本上得分
        self.train_score = None
        # 世界迭代次数
        self.actual_iteration = None

        
    def getW(self):
        '''
        获取分类面参数 w_0--w_i
        @return：元组类型，例如 (w_0, W)
        '''
        return (self.w_0, self.W)


    def getTrainResult(self):
        '''
        获取训练结果
        @return：元组类型。
                 第一个元素表示实际迭代次数；
                 第二个元素表示训练样本上的得分
        '''
        return (self.actual_iteration, self.train_score)


    def fit(self, Xtrain, Ytrain):
        '''
        训练测试样本
        Xtrain： 训练样本的数据，N * d 矩阵，第 i 行表示第 i 个数据
        Ytrain： 训练样本的标签，N * 1 矩阵，第 i 行表示第 i 个标签
        @return：(实际迭代的次数, 训练样本集上测试的得分)
        '''
        return (self.actual_iteration, self.train_score)
    

    def predict(self, Xtest):
        '''
        对测试样本进行预测
        Xtest：  测试样本的数据，N * d 矩阵，第 i 行表示第 i 个数据
        @return：测试样本的预测结果，N * 1 矩阵，第 i 行表示第 i 个标签
        '''
        N, D = Xtest.shape  # N：测试样本的数量；D：测试样本的维数

        # 获取感知机训练得到的参数
        if self.dimen == None:
            raise Exception("You should use model.fit() to train your data first!")
        
        # 检测训练样本和测试样本是否为同一维度
        if D != self.dimen:
            raise Exception("The dimensionality of Test data is different from Train's!")
        
        return self.Ypred
    
    
    def drawTwoDimenPic(self, traindata=True, testdata=False, line=True, color=['r','b','k','y','g','k','k'], threshold=0.0):
        '''
        画出包含训练集、测试集和分类面的二维图片，只适用于训练集为二维的情况（暂定）
        traindata：是否包含训练集数据
        testdata： 是否包含测试集数据
        line：     是否包含分类面
        threshold: 分类阈值，小于该阈值分为负类，否则为正类
        @return：  图像
        '''
        W = self.W
        w_0 = self.w_0
        Xtrain = self.Xtrain
        Ytrain = self.Ytrain
        Xtest = self.Xtest
        Ypred = self.Ypred
        dimen = self.dimen

        # 检测条件
        if dimen != 2:
            raise Exception("This method can use only when the dimensionality of Xtrain is 2 !")
            
        try:
            # 显示需要显示的训练集数据
            if traindata == True:
                train_pos_x1 = np.array([])
                train_pos_x2 = np.array([])
                train_neg_x1 = np.array([])
                train_neg_x2 = np.array([])
                train_mid_x1 = np.array([])
                train_mid_x2 = np.array([])
                for i in range(len(Ytrain)):
                    if Ytrain[i,0] > threshold:
                        train_pos_x1 = np.append(train_pos_x1, np.array([Xtrain[i,0]]))
                        train_pos_x2 = np.append(train_pos_x2, np.array([Xtrain[i,1]]))
                    elif Ytrain[i,0] < threshold:
                        train_neg_x1 = np.append(train_neg_x1, np.array([Xtrain[i,0]]))
                        train_neg_x2 = np.append(train_neg_x2, np.array([Xtrain[i,1]]))
                    else:
                        train_mid_x1 = np.append(train_mid_x1, np.array([Xtrain[i,0]]))
                        train_mid_x2 = np.append(train_mid_x2, np.array([Xtrain[i,1]]))
                plt.plot(train_pos_x1, train_pos_x2, '.' + color[0])
                plt.plot(train_neg_x1, train_neg_x2, '.' + color[1])
                plt.plot(train_mid_x1, train_mid_x2, '.' + color[2])
        except:
            raise Exception("You should use model.fit() to train your data first!")
        
        try:
            # 显示需要显示的测试集数据
            if testdata == True:
                test_pos_x1 = np.array([])
                test_pos_x2 = np.array([])
                test_neg_x1 = np.array([])
                test_neg_x2 = np.array([])
                test_mid_x1 = np.array([])
                test_mid_x2 = np.array([])
                for i in range(len(Ypred)):
                    if Ypred[i,0] > threshold:
                        test_pos_x1 = np.append(test_pos_x1, np.array([Xtest[i,0]]))
                        test_pos_x2 = np.append(test_pos_x2, np.array([Xtest[i,1]]))
                    elif Ypred[i,0] < threshold:
                        test_neg_x1 = np.append(test_neg_x1, np.array([Xtest[i,0]]))
                        test_neg_x2 = np.append(test_neg_x2, np.array([Xtest[i,1]]))
                    else:
                        test_mid_x1 = np.append(test_mid_x1, np.array([Xtest[i,0]]))
                        test_mid_x2 = np.append(test_mid_x2, np.array([Xtest[i,1]]))
                plt.plot(test_pos_x1, test_pos_x2, '.' + color[3])
                plt.plot(test_neg_x1, test_neg_x2, '.' + color[4])
                plt.plot(test_mid_x1, test_mid_x2, '.' + color[5])
        except:
            raise Exception("If you want to show Test data, " 
                            + "you should use model.predict() to test your data first!")
        
        # 显示分割面
        if line == True:
            if W[0] == 0 and W[1] == 0 and w_0 == 0:
                raise Exception("Error! W = [0, 0] and w_0 = 0")
            elif W[0] == 0:
                # 与x轴平行分来面
                line_x = np.linspace(Xtrain[:,0].min(), Xtrain[:,0].max())
                line_y = np.ones(line_x.shape) * w_0 / (- W[1])
            elif W[1] == 0:
                # 与y轴平行分类面
                line_y = np.linspace(Xtrain[:,1].min(), Xtrain[:,1].man())
                line_x = np.ones(line_y.shape) * w_0 / (- W[0])
            else:
                # 普通分类面
                x_min = Xtrain[:,0].min()
                x_max = Xtrain[:,0].max()
                y_min = Xtrain[:,1].min()
                y_max = Xtrain[:,1].max()
                if np.abs((x_max - x_min) * W[0] / (- W[1])) < np.abs(y_max - y_min):
                    line_x = np.linspace(x_min, x_max)
                    line_y = line_x * W[0] / (- W[1]) + w_0 / (- W[1])
                else:
                    line_y = np.linspace(y_min, y_max)
                    line_x = line_y * W[1] / (- W[0]) + w_0 / (- W[0])
            plt.plot(line_x, line_y, '-' + color[6])
        plt.show()